package logger

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"time"

	"gitee.com/gousing/helper/console"
	"gitee.com/gousing/helper/xstring"
	"gorm.io/gorm"
	gormlog "gorm.io/gorm/logger"
)

// ToSlog 转换 Logger 为 *slog.Logger
//   - 注意：原 log 自定义等级/调整等级/中间件/同步/关闭行为均为影响转换后的 *slog.Logger
func ToSlog(log *Logger) *slog.Logger {
	if log == nil {
		console.Error("ToSlog: logger is nil")
		Error("ToSlog: logger is nil")
		panic("ToSlog: logger is nil")
	} else if log.handlerAgent == nil {
		console.Error("ToSlog: logger.handlerAgent is nil")
		Error("ToSlog: logger.handlerAgent is nil")
		panic("ToSlog: logger.handlerAgent is nil")
	}
	return slog.New(log.handlerAgent)
}

type ginLogger struct {
	log *Logger
}

// ToGinLogger 转换 Logger 为gin.LoggerWriter
//   - 注意：原 log 自定义等级/调整等级/中间件/同步/关闭行为均为影响转换后的 *ginLogger
//   - Use:
//   - ginLog := logger.ToGinLogger(youLogger)
//   - gin.DefaultWriter = ginLog
//   - gin.DefaultErrorWriter = ginLog
//   - gin.DebugPrintRouteFunc = ginLog.DebugPrintRouteFunc
//   - gin.DebugPrintFunc = ginLog.DebugPrintFunc
//   - gin.DisableConsoleColor()
func ToGinLogger(log *Logger) *ginLogger {
	if log == nil {
		console.Error("ToGinLogger: logger is nil")
		Error("ToGinLogger: logger is nil")
		panic("ToGinLogger: logger is nil")
	} else if log.handlerAgent == nil {
		console.Error("ToGinLogger: logger.handlerAgent is nil")
		Error("ToGinLogger: logger.handlerAgent is nil")
		panic("ToGinLogger: logger.handlerAgent is nil")
	}
	ginLoggerWriter := &ginLogger{}
	ginLoggerWriter.log = log
	return ginLoggerWriter
}
func (l *ginLogger) logFormat(msgBuf *xstring.Buffer) (level string) {
	level = "DEBUG"
	//[GIN-debug] [ERROR] [WARNING]
	msgBuf.ReplaceString("[GIN]", "", 1)
	if msgBuf.ContainsString("[PANIC]") {
		level = "PANIC"
		msgBuf.ReplaceString("[PANIC]", "", 1)
	} else if msgBuf.ContainsString("[DPANIC]") {
		level = "DPANIC"
		msgBuf.ReplaceString("[DPANIC]", "", 1)
	} else if msgBuf.ContainsString("[FATAL]") {
		level = "FATAL"
		msgBuf.ReplaceString("[FATAL]", "", 1)
	} else if msgBuf.ContainsString("[ERROR]") {
		level = "ERROR"
		msgBuf.ReplaceString("[ERROR]", "", 1)
	} else if msgBuf.ContainsString("[WARNING]") {
		level = "WARN"
		msgBuf.ReplaceString("[WARNING]", "", 1)
	} else if msgBuf.ContainsString("[INFO]") {
		level = "INFO"
		msgBuf.ReplaceString("[INFO]", "", 1)
	} else if msgBuf.ContainsString("[GIN-debug]") {
		level = "DEBUG"
		msgBuf.ReplaceString("[GIN-debug]", "", 1)
	}
	msgBuf.ReplaceAllString("\r\t", "")
	msgBuf.ReplaceAllString("\t", "")
	msgBuf.ReplaceAllString("\r", "")
	msgBuf.ReplaceAllString("\n", "")
	msgBuf.TrimSpace()
	return level
}
func (l *ginLogger) Write(p []byte) (n int, err error) {
	if l == nil {
		return 0, errors.New("logger is nil")
	}
	if len(p) == 0 {
		return 0, nil
	}
	msgBuf := xstring.NewBuffer(len(p))
	msgBuf.Write(p)
	switch l.logFormat(msgBuf) {
	case "DEBUG":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelDebug, msgBuf.String())
	case "INFO":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelInfo, msgBuf.String())
	case "WARN":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelWarn, msgBuf.String())
	case "ERROR", "DPANIC", "PANIC", "FATAL":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelError, msgBuf.String())
	default:
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelInfo, msgBuf.String())
	}
	n = msgBuf.Len()
	msgBuf.Reset()
	return n, nil
}

func (l *ginLogger) DebugPrintFunc(format string, values ...any) {
	msg := fmt.Sprintf(format, values...)
	msgBuf := xstring.NewBuffer(len(msg))
	msgBuf.WriteString(msg)
	switch l.logFormat(msgBuf) {
	case "DEBUG":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelDebug, msgBuf.String())
	case "INFO":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelInfo, msgBuf.String())
	case "WARN":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelWarn, msgBuf.String())
	case "ERROR", "DPANIC", "PANIC", "FATAL":
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelError, msgBuf.String())
	default:
		l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelDebug, msgBuf.String())
	}
	msgBuf.Reset()
}
func (l *ginLogger) DebugPrintRouteFunc(httpMethod, absolutePath, handlerName string, nuHandlers int) {
	l.log.handlerAgent.Wrap(l.log.handlerAgent.options.CallerSkip+1, context.Background(), slog.LevelDebug, "register route",
		slog.String("method", httpMethod),
		slog.String("path", absolutePath),
		slog.String("handlerName", handlerName),
		slog.Int("handlers", nuHandlers))
}

type gormLogger struct {
	log                       *Logger
	slowThreshold             time.Duration
	isSilent                  bool
	skipCallerLookup          bool
	ignoreRecordNotFoundError bool
}

var _ gormlog.Interface = &gormLogger{}

// ToGormLogger 转换slog.Logger为 gormlog.Interface
//   - 注意：原 log 自定义等级/调整等级/中间件/同步/关闭行为均为影响转换后的 gormlog
//   - Use:
//   - db, err := gorm.Open(...)
//   - gormLog := logger.ToGormLogger(logger.GetDefault())
//   - db.Logger = gormLog
func ToGormLogger(log *Logger) gormlog.Interface {
	if log == nil {
		console.Error("ToGormLogger: logger is nil")
		Error("ToGormLogger: logger is nil")
		panic("ToGormLogger: logger is nil")
	} else if log.handlerAgent == nil {
		console.Error("ToGormLogger: logger.handlerAgent is nil")
		Error("ToGormLogger: logger.handlerAgent is nil")
		panic("ToGormLogger: logger.handlerAgent is nil")
	}
	return &gormLogger{
		log:                       log,
		slowThreshold:             time.Second,
		isSilent:                  false,
		skipCallerLookup:          false,
		ignoreRecordNotFoundError: true,
	}
}

func (g *gormLogger) LogMode(level gormlog.LogLevel) gormlog.Interface {
	switch level {
	case gormlog.Silent:
		// 完全禁用日志, 调整为最高日志等级
		g.isSilent = true
		g.log.handlerAgent.SetLevel(levelKeys[len(levelKeys)-1])
	case gormlog.Error:
		g.log.handlerAgent.SetLevel(slog.LevelError)
	case gormlog.Warn:
		g.log.handlerAgent.SetLevel(slog.LevelWarn)
	case gormlog.Info:
		g.log.handlerAgent.SetLevel(slog.LevelInfo)
	}
	return g
}
func (g *gormLogger) Info(ctx context.Context, msg string, data ...any) {
	if g.isSilent {
		return
	}
	g.log.handlerAgent.log(ctx, slog.LevelInfo, fmt.Sprintf(g.stringClean(msg), data...))
}
func (g *gormLogger) Warn(ctx context.Context, msg string, data ...any) {
	if g.isSilent {
		return
	}
	g.log.handlerAgent.log(ctx, slog.LevelWarn, fmt.Sprintf(g.stringClean(msg), data...))
}
func (g *gormLogger) Error(ctx context.Context, msg string, data ...any) {
	if g.isSilent {
		return
	}
	g.log.handlerAgent.log(ctx, slog.LevelError, fmt.Sprintf(g.stringClean(msg), data...))
}
func (g *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if g.isSilent {
		return
	}
	elapsed := time.Since(begin)
	switch {
	case err != nil && (!g.ignoreRecordNotFoundError || !errors.Is(err, gorm.ErrRecordNotFound)):
		sql, rows := fc()
		sql = g.stringClean(sql)
		g.log.handlerAgent.logAttrs(ctx, slog.LevelError, "Error SQL: "+err.Error(), slog.String("elapsed", elapsed.String()), slog.Int64("rows", rows), slog.String("sql", sql))
	case g.slowThreshold != 0 && elapsed > g.slowThreshold:
		sql, rows := fc()
		sql = g.stringClean(sql)
		g.log.handlerAgent.logAttrs(ctx, slog.LevelWarn, "Slow SQL > "+g.slowThreshold.String(), slog.String("elapsed", elapsed.String()), slog.String("sql", sql), slog.Int64("rows", rows))
	default:
		if g.log.Handler().Enabled(ctx, slog.LevelDebug) {
			sql, rows := fc()
			sql = g.stringClean(sql)
			g.log.handlerAgent.logAttrs(ctx, slog.LevelDebug, "Normal SQL: "+sql, slog.String("elapsed", elapsed.String()), slog.Int64("rows", rows), slog.String("sql", sql))
		}
	}
}
func (g *gormLogger) stringClean(v string) string {
	v = fmt.Sprint(v)
	msgBuf := xstring.NewBuffer(len(v))
	isLine := false
	isSlash := false
	isQuote := false
	for _, c := range v {
		switch c {
		case '\n', '\r', '\t':
			if !isLine {
				isLine = true
				msgBuf.WriteByte('\n')
			}
		case '\'':
			if !isQuote {
				isQuote = true
				msgBuf.WriteByte('\'')
			}
		case '\\':
			if !isSlash {
				isSlash = true
				msgBuf.WriteByte('\\')
			}
		default:
			isLine = false
			isQuote = false
			isSlash = false
			msgBuf.WriteRune(c)
		}
	}
	return msgBuf.TrimSpace().String()
}
